package com.pig4cloud.pigx.contract.executer.order.initializer.impl;

import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.contants.SystemConfigConstants;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.seata.utils.SeataUtils;
import com.pig4cloud.pigx.contract.bo.ContractExistPositionInfoBO;
import com.pig4cloud.pigx.contract.bo.ContractOrderNewBO;
import com.pig4cloud.pigx.contract.constant.ContractMqConstants;
import com.pig4cloud.pigx.contract.constant.I18nMessages;
import com.pig4cloud.pigx.contract.dto.ContractHierarchyDTO;
import com.pig4cloud.pigx.contract.entity.ContractOrder;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.executer.order.initializer.OrderInitializer;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.mapper.ContractOrderMapper;
import com.pig4cloud.pigx.contract.mapper.ContractPositionMapper;
import com.pig4cloud.pigx.contract.mapstruct.ContractOrderStruct;
import com.pig4cloud.pigx.contract.mongo.entity.ContractOrderMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import com.pig4cloud.pigx.contract.vo.ContractHierarchyVO;
import com.pig4cloud.pigx.contract.vo.ContractOrderInitResVO;
import com.pig4cloud.pigx.wallet.dto.WalletContractAccountChangeDTO;
import com.pig4cloud.pigx.wallet.enums.WalletContractAccountDetailEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.mongodb.core.MongoTemplate;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Objects;

/**
 * 订单初始化器抽象实现
 *
 * @since 2022/8/17 11:17
 */
@Slf4j
@AllArgsConstructor
public abstract class AbstractOrderInitializer implements OrderInitializer {

    protected final ContractOrderMapper contractOrderMapper;
    protected final ContractPositionMapper contractPositionMapper;

    protected final MongoTemplate mongo;
    protected final AmqpTemplate rabbit;

    protected final CommonUtil commonUtil;
    protected final MysqlUtil mysqlUtil;
    protected final SecurityUtil securityUtil;
    protected final ContractUtils contractUtils;

    /**
     * 初始化订单
     *
     * @param bo bo
     * @since 2022/8/17 11:15
     */
    @Override
    public void init(ContractOrderNewBO bo) {
        log.info("执行的Initializer：" + this.getClass().getSimpleName());

        // 预初始化数据
        preInitParam(bo);
        // 校验bo（共有）
        validParam(bo);
        // 校验币对相关信息（共有）。查询币对信息，校验
        validSymbol(bo);
        // 校验止盈止损
        validStopOrder(bo);
        // 校验仓位相关信息（共有）。查询仓位信息，校验
        validPosition(bo);

        // 根据bo初始化数据（共有）
        initParam(bo);
        // mysql持久化
        saveToMysql(bo);

        // 开仓时冻结保证金
        if (bo.getIsOpen()) {
            // 开启分布式事务
            SeataUtils.startGlobalTx();
            frozenAssetWhenOpen(bo);
        }
    }

    /**
     * 校验止盈止损信息
     *
     * @param bo bo
     * @since 2022/9/2 21:40
     */
    protected void validStopOrder(ContractOrderNewBO bo) {
        // 只有开仓有止盈止损
        if (!bo.getIsOpen()) {
            return;
        }

        // 没有止盈止损，直接结束
        if (!bo.getIsConditionPending()) {
            return;
        }

        ContractSymbolMongo symbol = bo.getSymbol();
    }

    /**
     * 预初始化数据
     *
     * @param bo bo
     * @since 2022/8/29 19:00
     */
    protected void preInitParam(ContractOrderNewBO bo) {
        // 获取币对信息
        ContractSymbolMongo symbol = bo.getSymbol();
        if (Objects.isNull(symbol)) {
            symbol = contractUtils.getSymbolById(bo.getSymbolId());
            ContractUtils.checkSymbol(symbol);
            bo.setSymbol(symbol);
        }

        // 设置数量和价格的精度
        bo.setNum(bo.getNum().setScale(symbol.getTokenCurAccuracy(), SystemConfigConstants.tokenCurRoundingMode));
        bo.setPrice(bo.getPrice().setScale(symbol.getMainCurAccuracy(), SystemConfigConstants.mainCurRoundingMode));

        // 判断是否开仓
        bo.setIsOpen(Objects.equals(ContractOrderEnum.OperationType.OPEN.getValue(), bo.getOperationType()));

        // 是否是机器人，没有指定的都不是
        if (Strings.isEmpty(bo.getIsRobot())) {
            bo.setIsRobot(CommonDictEnum.YesOrNo.NO.getValue());
        }
    }

    /**
     * 计算需要冻结的合约账户金额，开始冻结
     *
     * @param bo bo
     * @since 2022/8/17 18:46
     */
    protected void frozenAssetWhenOpen(ContractOrderNewBO bo) {
        WalletContractAccountChangeDTO param = new WalletContractAccountChangeDTO(bo.getUserId(),
                bo.getFrozenAmount(),
                null,
                bo.getSymbolId(),
                bo.getSymbolName(),
                null,
                WalletContractAccountDetailEnum.OperationType.FREEZE,
                false);
        R<?> r = contractUtils.changeAsset(Collections.singletonList(param));
        if (!r.getIsSuccess()) {
            throw new MqAckException(MessageUtils.get(I18nMessages.INSUFFICIENT_CONTRACT_ACCOUNT));
        }

        // log.info("调用了钱包模块Feign接口，冻结合约账户保证金：" + frozenAmount);
    }

    /**
     * 获得开仓时需要冻结的金额
     *
     * @param bo bo
     * @return {@link BigDecimal}
     * @since 2022/8/22
     */
    protected BigDecimal getFrozenAmountWhenOpen(ContractOrderNewBO bo) {
        return bo.getMargin();
    }

    /**
     * 初始化数据
     *
     * @param bo bo
     * @since 2022/8/16 22:22
     */
    protected void initParam(ContractOrderNewBO bo) {
        ContractSymbolMongo symbol = bo.getSymbol();

        // 币对名称
        bo.setSymbolName(symbol.getName());
        // 判断是否是买单
        bo.setIsBuy(ContractUtils.judgeIsBuy(bo.getOperationType(), bo.getSide()));
        // 计算订单面值，价格 * 数量
        bo.setOrderValue(OrderCalculator.getOrderValue(bo.getPrice(), bo.getNum()));
        // 计算未成交金额，价格 * 数量
        bo.setNoDealAmount(bo.getOrderValue());
        // 成交金额
        bo.setDealAmount(BigDecimal.ZERO);
        // 已成交数量
        bo.setDealNum(BigDecimal.ZERO);
        // 未成交数量
        bo.setNoDealNum(bo.getNum());
        // 手续费
        bo.setHandlingFee(BigDecimal.ZERO);
        // 冻结合约账户保证金
        bo.setFrozenAmount(BigDecimal.ZERO);
        // 强平清算费
        bo.setLiquidateFee(BigDecimal.ZERO);
        // 状态，委托中
        bo.setStatus(ContractOrderEnum.Status.ON_GOING.getValue());
        // 基础数据
        securityUtil.initCreateAbout(bo);

        // 开仓，计算保证金
        if (bo.getIsOpen()) {
            bo.setMargin(OrderCalculator.getMargin(bo.getOrderValue(), bo.getMultiple()));
            // 冻结金额
            bo.setFrozenAmount(getFrozenAmountWhenOpen(bo));
        } else {
            // 平仓，设置保证金为0
            bo.setMargin(BigDecimal.ZERO);
        }
    }

    /**
     * 校验仓位信息
     *
     * @param bo bo
     * @since 2022/8/16 21:25
     */
    protected void validPosition(ContractOrderNewBO bo) {
        ContractSymbolMongo symbol = bo.getSymbol();

        // 开仓
        if (bo.getIsOpen()) {
            // 查询已有的仓位杠杆倍数、仓位模式数据
            ContractExistPositionInfoBO existPositionInfo = contractUtils.getExistPositionInfo(bo.getUserId(),
                    bo.getSymbolId());

            // 存在仓位、存在已有的杠杆倍数，比较杠杆倍数
            if (existPositionInfo.getHasPosition() &&
                    !Objects.isNull(existPositionInfo.getMultiple()) && existPositionInfo.getMultiple().compareTo(bo.getMultiple()) > 0) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.CANNOT_BE_LOWER_THAN_THE_EXISTING_LEVERAGE_MULTIPLE, existPositionInfo.getMultiple()));
            }
            // 存在已有的仓位模式，比较仓位模式
            if (Strings.isNotEmpty(existPositionInfo.getMode()) && !Objects.equals(existPositionInfo.getMode(), bo.getPositionMode())) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.EXISTING_POSITION_PATTERN, ContractPositionEnum.Mode.findName(existPositionInfo.getMode())));
            }

            // 累计总持仓数量
            double positionTotalNum = contractUtils.incrPositionTotalNum(bo.getUserId(), symbol.getName(), bo.getNum());
            bo.setHasAddPositionTotalNum(true);
            // 判断最大持仓数量
            ContractHierarchyDTO dto = new ContractHierarchyDTO();
            dto.setSymbolId(symbol.getId());
            dto.setMultiple(bo.getMultiple());
            ContractHierarchyVO hierarchy = contractUtils.getHierarchy(dto);
            ContractUtils.checkHierarchy(hierarchy, positionTotalNum, bo.getMultiple(), symbol.getName());
        }

        // 查询已有仓位
        ContractPosition condition = ContractUtils.getCurrentPositionCondition(null,
                bo.getUserId(),
                bo.getSymbolId(),
                bo.getPositionMode(),
                bo.getSide());
        ContractPosition currentPosition = contractUtils.getOneCurrentPosition(condition, false);
        ContractUtils.checkCurrentPosition(currentPosition, bo.getIsOpen());

        // 如果是平仓
        if (!bo.getIsOpen()) {
            // 修改仓位的平仓中数量
            contractUtils.addPositionClosingNum(bo.getPositionId(), bo.getNum());
        }
    }

    /**
     * 校验币对信息
     *
     * @param bo bo
     * @since 2022/8/16 21:05
     */
    protected void validSymbol(ContractOrderNewBO bo) {
        ContractSymbolMongo symbol = bo.getSymbol();

        // 最大杠杆倍数
        if (symbol.getMultiple() < bo.getMultiple()) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.MAXIMUM_LEVERAGE_MULTIPLE_IS, symbol.getMultiple()));
        }
        // 最小下单数量
        if (symbol.getMinOrderNum().compareTo(bo.getNum()) > 0) {

            throw new RuntimeException(MessageUtils.get(I18nMessages.MINIMUM_ORDER_QUANTITY_IS, symbol.getMinOrderNum().stripTrailingZeros().toPlainString()));
        }

        // 换算张数
        if (symbol.getSheet() != null) {
            BigDecimal num = bo.getNum().multiply(symbol.getSheet());
            // 最小下单数为一张 并且必须是整张
            try {
                // 转换报错视为非整张
                long exact = num.longValueExact();
                if (exact < 1) {
                    throw new RuntimeException(MessageUtils.get(I18nMessages.MINIMUM_ORDER_SHEET_QUANTITY_IS));
                }
            } catch (Exception e) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.MINIMUM_ORDER_SHEET_QUANTITY_IS));
            }

        }

        // 根据是否有止盈止损条件，判断是否为条件挂单
        boolean isConditionPending = ContractUtils.isConditionPending(bo.getHasStopProfit(), bo.getHasStopLost());
        bo.setIsConditionPending(isConditionPending);
    }

    /**
     * 校验bo
     *
     * @param bo bo
     * @since 2022/8/16 18:16
     */
    protected void validParam(ContractOrderNewBO bo) {
        // 用户id
        if (Objects.isNull(bo.getUserId())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.UNABLE_TO_OBTATIN_USER_INFORMATION));
        }

        if (BigDecimal.ZERO.compareTo(bo.getNum()) >= 0) {
            throw new RuntimeException("数据异常，num不能小于等于0");
        }

        // 操作类型 = 开仓
        if (bo.getIsOpen()) {
            if (Strings.isEmpty(bo.getHasStopProfit())) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "hasStopProfit"));
            }

            // 是否止盈 = 是
            if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), bo.getHasStopProfit())) {
                if (Objects.isNull(bo.getStopProfitTrigger())) {
                    throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_CANNOT_BE_EMPTY, "stopProfitTrigger"));
                }
                if (Strings.isEmpty(bo.getStopProfitType())) {
                    throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_CANNOT_BE_EMPTY, "stopProfitType"));
                }
            }

            if (Strings.isEmpty(bo.getHasStopLost())) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "hasStopProfit"));
            }
            // 是否止损 = 是
            if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), bo.getHasStopLost())) {
                if (Objects.isNull(bo.getStopLostTrigger())) {
                    throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_CANNOT_BE_EMPTY, "stopLostTrigger"));
                }
                if (Strings.isEmpty(bo.getStopLostType())) {
                    throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_CANNOT_BE_EMPTY, "stopLostType"));
                }
            }

        } else {
            // 操作类型 = 平仓
            if (Objects.isNull(bo.getPositionId())) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_CANNOT_BE_EMPTY, "positionId"));
            }
        }
    }

    /**
     * 初始化成功
     *
     * @param bo bo
     * @return {@link ContractOrderInitResVO}
     * @since 2022/8/17 11:31
     */
    @Override
    public ContractOrderInitResVO success(ContractOrderNewBO bo) {
        // 存入mongodb的 contract_new_order 表
        saveToMongo(bo);

        // 提交分布式事务
        SeataUtils.commitGlobalTx();
        // 提交本地事务
        mysqlUtil.commitLocalTx();

        // 发送mq消息
        sendMq(bo);

        // throw new RuntimeException("rollback");
        ContractOrderInitResVO res = new ContractOrderInitResVO();
        res.setId(bo.getId());
        res.setClientOrderId(bo.getClientOrderId());

        return res;
    }

    /**
     * 发送mq消息
     *
     * @param bo bo
     * @since 2022/8/17 20:49
     */
    protected void sendMq(ContractOrderNewBO bo) {
        // 发送新增订单消息
        ContractOrderMongo msg = new ContractOrderMongo();
        msg.setId(bo.getId());
        msg.setIsBuy(bo.getIsBuy());
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_NEW,
                null,
                msg);

        // 发送深度图参数消息。深度图功能暂时弃用该消息队列，2022.9.7（可通过该条注释检索到其它深度图消息发送代码位置）
		/*contractUtils.sendDepthMap(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_NEW_DEPTH_MAP,
				null,
				bo.getSymbolName(),
				bo.getSide(),
				bo.getNoDealNum(),
				bo.getPrice());*/

        // 开仓时需要冻结金额，发送重算全仓强平价消息
        if (bo.getIsOpen()) {
            contractUtils.sendPositionCrossCalculate(bo.getUserId());
        }
    }

    /**
     * 保存到mongodb
     *
     * @param bo bo
     * @since 2022/8/17 20:48
     */
    protected void saveToMongo(ContractOrderNewBO bo) {
        ContractOrderMongo orderMongo = ContractOrderStruct.INSTANCE.orderNewBO2Mongo(bo);
        // 是否是新订单
        orderMongo.setIsNew(true);
        mongo.save(orderMongo, ContractUtils.getOrderBookName(bo.getIsBuy()));
    }

    /**
     * 保存到mysql
     *
     * @param bo bo
     * @since 2022/8/17 20:47
     */
    protected void saveToMysql(ContractOrderNewBO bo) {
        ContractOrder entity = ContractOrderStruct.INSTANCE.orderNewBO2Entity(bo);
        if (contractOrderMapper.insert(entity) != 1) {
            // 订单信息初始化失败
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_INITIALIZE_THE_ORDER_INFORMATION));
        }
    }

    /**
     * 初始化失败
     *
     * @param bo bo
     * @param e  异常
     * @since 2022/8/17 11:31
     */
    @Override
    public void fail(ContractOrderNewBO bo, Exception e) {
        // 增加过挂单数量，此时减少
        if (Boolean.TRUE.equals(bo.getHasAddPendingNum())) {
            contractUtils.incrOrderPending(bo.getUserId(), -1);
        }

        // 增加过条件单挂单数量，此时减少
        if (Boolean.TRUE.equals(bo.getHasAddConditionPendingNum())) {
            contractUtils.incrOrderConditionPending(bo.getUserId(), -1);
        }

        // 累计过总持仓数量，此时减少
        if (Boolean.TRUE.equals(bo.getHasAddPositionTotalNum())) {
            contractUtils.incrPositionTotalNum(bo.getUserId(),
                    bo.getSymbol().getName(),
                    bo.getNum().negate());
        }
    }
}
